Chapter 25
WinInet Programming

by Davis Chapman

In This Chapter

  Web Application Protocols and WinInet 886
  Internet Session Basics 890
  Application-Level Functionality 909

When you are building an application using Winsock functionality, you have to know and work with more than just the basic network communications. You also need to know and work with the appropriate protocol for the application that you are writing. An application protocol is the conversation that takes place between the client application and the server with which it is exchanging messages. This conversation is usually a series of commands issued by the client (and occasionally by the server) to which the server responds. These commands have to be formatted in a specific way, and often have to follow a specific order.

With the prevalence of the Web in computing now, Microsoft decided to ease the burden of incorporating Web functionality into any Windows applications. As a result of this decision, Microsoft introduced the Windows Internet (WinInet for short) extensions to the Windows API. By encapsulating the primary Web protocols as extensions to the Windows API, Microsoft has greatly simplified the process of Web- and Internet-enabling just about any application.

Web Application Protocols and WinInet

When the Web first began, few Web sites were actually running the Hypertext Transfer Protocol (HTTP) upon which the Web is based. Instead, what you had was a mixture of sites running mostly the File Transfer Protocol (FTP) and the Gopher protocols. Both of these application protocols were able to deliver the basic HTML Web pages to a browser, so all were in fairly equal use. It has only been through the passage of time, and the explosive growth of the Web, that the FTP and Gopher protocols have fallen out of use, as almost all sites are now running HTTP servers for their Web sites. Because of these origins, Web browsers started out having to support all three of these Internet application protocols. With the effort to Internet-enable other applications, it has been assumed that all three of these protocols would need to be supported.

Hypertext Transfer Protocol

The Hypertext Transfer Protocol started out as one of the simplest Internet application protocols. When the Web first started to become popular, the HTTP protocol consisted of three basic commands, GET, POST, and HEAD. These three commands performed the basic functions of the Web. The GET command would request a specific object from the Web server, such as an HTML page or image file, and the Web server would respond by sending the object through the same socket connection. The HEAD command would cause the Web server to respond with what was basically a description of the object that had been requested, like the type of file the object was (HTML, image, and so on), its size, and when it was last updated. The final command, POST, would be used to send data to the Web server, which would usually pass this information along to a separate application to process and generate some dynamic output to be returned to the Web browser.

Along with a small set of commands that could be issued to the Web server, the HTTP protocol was also a single-command-per-connection protocol. In other words, a Web browser would connect to a Web server, send one of these three commands, receive the response, and close the connection. If a Web page contained three images, the Web browser would make at least four connections to the Web server: one for the Web page and one for each of the three images.

Since those early beginnings, the HTTP protocol has undergone several changes. It now consists of several commands, including commands that can be used to place Web pages and other objects onto the Web server, and can keep the connection open for multiple commands. Along with all the changes in the way commands are sent and responded to, the HTTP protocol has also added requirements on how the commands and responses are formatted. All in all, there have been many changes in the HTTP protocol, and there will probably be more to come.

File Transfer Protocol

The File Transfer Protocol is a bit more involved and has been around for quite a while longer than the HTTP protocol. FTP is used for moving files between two or more computers, regardless of the type of computers involved. It provides the client application with the capability of moving around in the server’s file system, getting files from and placing files onto the server wherever the client has permission to do so. If the user has appropriate permissions on the FTP server, the user can even delete files from the server using the FTP protocol.

Because of the more extensive requirements of the FTP protocol, it is quite a bit different from the HTTP protocol. First, the client application has to log in to the FTP server, providing a username and password before access is granted to the client application. Second, the FTP protocol has an extensive set of commands for navigating through the server’s file system; getting directory listings; and moving, deleting, and renaming files, along with the basic file transfer commands to move files from the client computer to the server and vice versa.

A third aspect of the FTP protocol is that it uses more than one socket connection to perform its tasks. A single socket connection is opened by the client that remains open until the session is completed. This is known as the command connection. A second socket connection is opened either by the server or the client that is used to move any data between the two computers, as in Figure 25.1. This second connection is used to pass files between the two computers. It is also used to send directory listings from the server to the client. This connection is opened each time the client issues a command to the server that involves any data being passed between the two computers, and is closed when all of the data has been passed for that one command.


Figure 25.1  The FTP protocol uses two socket connections.

Gopher Protocol

The Gopher protocol is somewhere in between the FTP and HTTP protocols in terms of functionality and complexity. It can be used like the FTP protocol to browse server file systems, and to retrieve files from the server. It can also be used to search multiple servers for files on a particular subject. The Gopher protocol was originally developed as a document search and delivery mechanism, very much the same purpose for which the HTTP protocol was developed (and around the same time, too). The differences between the Gopher and HTTP protocols are that the Gopher protocol was originally more involved, containing much of the file system navigation functionality from the FTP protocol, and the HTTP protocol was tightly coupled with the Hypertext Markup Language (HTML) format for document presentation. It was because the HTTP/HTML combination lent itself to graphical displays—more than the Gopher protocol did—that the HTTP/HTML combination became the dominant protocol for the Web.


Note:  

The Gopher protocol is one of the few Internet application protocols whose name is not an acronym for a functional description of the protocol. The Gopher protocol was developed at the University of Minnesota, Minnesota being known as the “Gopher state.” As a result, the Gopher protocol took its name from where it was developed, not from what it does.




WinInet API and MFC

Microsoft created the WinInet API to provide an easy-to-use interface for these three Internet application protocols. They provided a way of interacting with any of these protocols in much the same way that you would interact with a file on a local drive. If you want, you can issue any of the commands for any one of these protocols, or you can just pass a URL (Uniform Resource Locator) and read in the resulting object as you would read in any other file.

If you need to get more specific in your application functionality, and you need to use the specific protocol functionality of any of these three protocols, you can do this through the WinInet API just as easily as you can ignore these protocols. You don’t need to declare or open a single socket connection: Just start an Internet session and then issue the appropriate protocol commands.

When you work with the MFC WinInet classes, you’ll have a collection of classes that you can use in numerous combinations. One of the base classes that you’ll always use is the CInternetSession class. This class will be used in all of your WinInet applications, regardless of what else you might use.

You’ll use your CInternetSession class to open a connection class. This connection class can be a generic connection class, CInternetConnection, but it will more likely be a protocol-specific connection, CHttpConnection, CFtpConnection, or CGopherConnection. After you have a connection class open, you can issue protocol-specific commands to perform the various tasks that each of the protocols can do.

Through the connection object, you can open a file object, CInternetFile, CGopherFile, or CHttpFile, which understands the application protocol being used to retrieve the file. The CInternetFile class is used to read and write files using the FTP protocol. The other two classes, both descendants of the CInternetFile class, understand the differences between the FTP protocol and their respective protocols.

With the FTP and Gopher protocols, there are a group of classes that have file-finding and file-locating functionality. These classes are CFileFind, as the base class, and CFtpFileFind and CGopherFileFind as protocol-specific descendants. In addition, there is the CGopherLocator, which is used to get information about the Gopher server and what kind of files may be retrieved from the Gopher server.

The final WinInet MFC class is the CInternetException class, which is the exception class that is thrown on any WinInet exceptions. The CInternetException class can be used to handle any of the exceptions thrown by any of the other WinInet MFC classes.

Internet Session Basics

The starting point for any WinInet MFC application is with the base Internet classes, CInternetSession, CInternetFile, CInternetException, and CInternetConnection. With the exception of the CInternetConnection class, these are the classes that you have to use in any MFC WinInet application.

CInternetSession

In every WinInet application, you’ll start with the CInternetSession class. This class is the base from which all other operations will be spawned. You’ll start all of your WinInet functionality by creating an instance of the CInternetSession class, and then use it to create and open the various other WinInet classes.

Creating a CInternetSession Instance

Creating an instance of the CInternetSession class can be simple, or very involved, depending on the configuration with which your application needs to be able to work. The simple way of creating an instance is to simply declare an instance of the CInternetSession class, or of a descendant that you have created. This will create the instance using all of the default settings.

If your application needs to be able to work in a more complex environment, such as going through a proxy server, you may need to take more control of how you create the CInternetSession instance.

The first of the parameters that you can pass to the class constructor is the name of your application. This is used to identify your application to the server when you connect and download or upload files. You can pass in any name that you want to use, like “Davis’ Internet Utility” or “World’s Greatest FTP Client.” If you do not supply this parameter, or pass in a null, the AfxGetAppName function will be called to get your application name.

The second parameter is the context ID in which your Internet session will be run. This context ID will be passed back to your application with any overridable functions to identify the CInternetSession instance that is passing an event to your code.

The third parameter specifies whether or not to use a proxy server to connect to the Internet. There are three possible settings for this parameter, listed in Table 25.1.

Table 25.1 Access Type Parameter Values

Value Description

INTERNET_OPEN_TYPE_PRECONFIG The default value; this setting uses the access configuration in the registry.
INTERNET_OPEN_TYPE_DIRECT Direct connection to the Internet.
INTERNET_OPEN_TYPE_PROXY Connects to the Internet through a CERN proxy server.

The fourth parameter is the name of the proxy server to be used if the access type is specified as through a proxy server. If you also have a list of server addresses that need to bypass the proxy server, this list is to be passed as the fifth parameter.

The sixth and final parameter that can be passed to the CInternetSession constructor is a flag that can be used to specify a few options. These options are listed in Table 25.2.

Table 25.2 WinInet Session Options

Value Description

INTERNET_FLAG_DONT_CACHE Specifies that all data requested not be cached, either locally or in any gateway server through which the data passes.
INTERNET_FLAG_ASYNC Specifies that all WinInet operations be performed asynchronously. When an operation is completed, the status callback function is called with the status of INTERNET_STATUS_REQUEST_COMPLETE. This status callback function will be covered later in the section “Keeping Up with the Session Status.”
INTERNET_FLAG_OFFLINE All download requests are filled from the persistent cache only. If the requested file is not in the cache, the request fails.

After you have finished all Internet operations, you need to call the Close method to clean up the Internet session object before destroying the CInternetSession object.



Opening URLs and Connections

When you have a CInternetSession object, what can you do with it? You have several choices. You can open a URL to download a file, or you can open a connection to one of the three types of servers and interact with it. When you have a connection open, you can perform any of the standard actions that are available to a client of the particular application that you have connected to.

OpenURL

The most basic of the actions that you can perform is to open a URL. This is done with the OpenURL method, which returns a pointer to a CStdioFile object, which you can read in using the various methods of either that particular class, or the protocol-specific descendants.

The first parameter that you pass to the OpenURL function is the URL to be opened. This should be a complete URL, with the protocol, server, and object to be retrieved. The protocol determines the file type that is returned. The protocols listed in Table 25.3 are the only protocols available for use with the OpenURL function.

Table 25.3 OpenURL Protocol File Types

URL Protocol File Type Returned

file:// CStdioFile*
http:// CHttpFile*
gopher:// CGopherFile*
ftp:// CInternetFile*


Note:  

The file:// protocol retrieves files from local or networked drives.


The second parameter is the context flag that will be returned with any overridden event function. The third parameter is a flag that can be used to specify how the URL specified is retrieved. The possible values for this flag are listed in Table 25.4.

Table 25.4 OpenURL Options

Value Description

INTERNET_FLAG_TRANSFER_ASCII The default setting; transfers the file as ASCII text.
INTERNET_FLAG_TRANSFER_BINARY Transfers the file as a binary file.
INTERNET_FLAG_RELOAD Retrieves the file from the server, even if cached locally.
INTERNET_FLAG_DONT_CACHE Don’t cache the file, either locally or on any gateway or server.
INTERNET_FLAG_SECURE Applicable for HTTP URLs only; this flag specifies to use Secure Sockets Layer or PCT for a secure transaction.
INTERNET_OPEN_FLAG_USE_EXISTING_CONNECT If possible, reuse the connection to the server for new requests generated with the OpenURL function, instead of opening a new connection for each request.
INTERNET_FLAG_PASSIVE Applicable for FTP URLs; uses passive FTP options.

The last two parameters are for use with HTTP servers. The fourth parameter is a string containing any additional headers to be sent to the HTTP server. The fifth parameter is the length of the additional headers passed in the fourth parameter. A typical use of the OpenURL function would look like the following:

CHttpFile* pURLFile;
CInternetSession isSession;
CString sURL;
CString sHeaders;

sURL = “http://www.myserver.com/file_to_get.htm”;
sHeaders = “X-Application=My Web Browser”;
pURLFile = isSession.OpenURL(sURL, 1,
        INTERNET_FLAG_TRANSFER_ASCII | INTERNET_FLAG_RELOAD,
        sHeaders, sHeaders.GetLength());

Another, more basic use of the OpenURL method would supply just the URL, as follows:

CHttpFile* pURLFile;
CInternetSession isSession;
CString sURL;

sURL = “http://www.myserver.com/file_to_get.htm”;
pURLFile = isSession.OpenURL(sURL);

GetHttpConnection

The GetHttpConnection method is used to create a CHttpConnection object. This method opens the connection to the specified HTTP server, but you have to call all of the appropriate CHttpConnection methods to perform the actions you need. The GetHttpConnection method returns a pointer to a CHttpConnection object.

The first parameter is the server name to be connected to. This is the only required parameter for the GetHttpConnection method. The second parameter is the port to use to connect to the HTTP server. The third parameter is the username to connect as, if the HTTP server requires a login for access. The fourth parameter is the password for the login specified. A typical use of the GetHttpConnection method is as follows:

CHttpConnection* pConnect;
CInternetSession isSession;
CString sServer;

sServer = “www.myserver.com ”;
pConnect = isSession.GetHttpConnection(sServer);

A more involved use for a server that required login access might look like this:

CHttpConnection* pConnect;
CInternetSession isSession;
CString sServer;
CString sUserName;
CString sPassword;

sServer = “www.myserver.com ”;
sUserName = “MyName”;
sPassword = “MyPassword”;
pConnect = isSession.GetHttpConnection(sServer, 80,
        sUserName, sPassword);

GetFtpConnection

The GetFtpConnection method is very similar to the GetHttpConnection method. It requires all of the same information, with one addition, only in a slightly different order. The GetFtpConnection method returns a pointer to a CFtpConnection object.

As with the GetHttpConnection, the first parameter is the server name. This is the only required parameter for this method, although it’s likely that the FTP server will require the second and third parameters also. The second parameter is the username to be used to log in to the FTP server. The third parameter is the password to be used. The fourth parameter is the port to use to connect to the FTP server. There is a fifth parameter, a Boolean value, which, if TRUE, specifies to use the FTP passive mode. A typical use of the GetFtpConnection method is as follows:

CFtpConnection* pConnect;
CInternetSession isSession;
CString sServer;
CString sUserName;
CString sPassword;

sServer = “ftp.myserver.com ”;
sUserName = “MyName”;
sPassword = “MyPassword”;
pConnect = isSession.GetFtpConnection(sServer,
        sUserName, sPassword);



GetGopherConnection

The GetGopherConnection is just like the GetFtpConnection method, with several necessary differences. The first difference is that the GetGopherConnection returns a pointer to an instance of the CGopherConnection class, as you would expect it to. The second difference is that it does not take the fifth parameter available with the GetFtpConnection method because that parameter is an FTP-specific option.

In short order, the parameters for the GetGopherConnection start with the server name. The second and third parameters are the username and password to be used to log in to the Gopher server. The fourth parameter is the port to connect to on the Gopher server. A typical use of this method is as follows:

CGopherConnection* pConnect;
CInternetSession isSession;
CString sServer;
CString sUserName;
CString sPassword;

sServer = “gopher.myserver.com ”;
sUserName = “MyName”;
sPassword = “MyPassword”;
pConnect = isSession.GetGopherConnection(sServer,
        sUserName, sPassword);

Working with Cookies

Cookies are a fact of life on the Web. They are small pieces of information that are placed on your computer by Web sites that are sent to specific Web addresses whenever you visit a specific site. Cookies have been regarded with suspicion by some, although they are considered an efficient solution to many Web programming problems by others. Regardless of which side you might be on in the argument over cookies, the WinInet MFC classes provide you with functionality to incorporate them into your applications.

The first of the cookie functions is the SetCookie function. This function will place a cookie on the computer your application is running on. The SetCookie function takes three parameters. The first parameter is the URL to which the cookie is to be sent. The second parameter is the name of the cookie, and the third parameter is the value of the cookie. A typical use of the SetCookie function is as follows:

CInternetSession isSession;
CString sURL;
CString sCookieName;
CString sCookieValue;

sURL = “www.myserver.com ”;
sCookieName = “MyCookie”;
sCookieValue = “MyValue”;
if(isSession.SetCookie(sURL, sCookieName, sCookieValue)
{
.
.
.
}

The next cookie function enables you to retrieve a cookie from your computer. This function, GetCookie, has two slightly different versions. Both versions have the URL as the first parameter, and the cookie name to retrieve as the second parameter. The first version requires a string pointer as the third argument, with the size of the buffer as the fourth parameter. The second version requires the address of a CString object as the third parameter.

If you are using the first version of the GetCookie function, you’ll probably also want to use the third cookie function, GetCookieLength, to determine the size of the buffer needed. The GetCookieLength function takes the URL and cookie name as its only two parameters, and returns the cookie size. A typical usage of these two functions is as follows:

CInternetSession isSession;
CString sURL;
CString sCookieName;
char* sCookieValue;
DWORD dwCookieLength;

sURL = “www.myserver.com ”;
sCookieName = “MyCookie”;
dwCookieLength = GetCookieLength(sURL, sCookieName);
sCookieValue = new char[(dwCookieLength + 1)];
if(isSession.GetCookie(sURL, sCookieName, sCookieValue,
      (dwCookieLength + 1))
{
.
.
.
}

If you want to use the second version of the GetCookie function, it might look like this:

CInternetSession isSession;
CString sURL;
CString sCookieName;
CString sCookieValue;

sURL = “www.myserver.com ”;
sCookieName = “MyCookie”;
if(isSession.GetCookie(sURL, sCookieName, &sCookieValue)
{
.
.
.
}

Keeping Up with the Session Status

Several functions are available for keeping track of the status of the Internet communications of your application. If you create your own descendant class from the CInternetSession class, there is a method that you can override called OnStatusCallback. This method is called whenever there is an operation pending, with information about the status of the operation. Before the OnStatusCallback callback function will be called, you have to enable it using the EnableStatusCallback function. The EnableStatusCallback function takes a single Boolean variable, enabling or disabling the OnStatusCallback callback function.

The OnStatusCallback function is defined as follows:

virtual void OnStatusCallback(DWORD dwContext,
        DWORD dwInternetStatus, LPVOID lpvStatusInformation,
        DWORD dwStatusInformationLength);

The first parameter passed to it is the context that was provided to the class constructor, or passed in with the OpenURL function. The second parameter is a status indicator, listed in Table 25.5. The third parameter is a pointer to a buffer containing additional information that is pertinent to the callback, with the fourth being the size of the buffer.

Table 25.5 Internet Status Indicators

Value Description

INTERNET_STATUS_RESOLVING_NAME Looking up the IP address for the server.
INTERNET_STATUS_NAME_RESOLVED The IP address for the server has been found.
INTERNET_STATUS_CONNECTING_TO_SERVER Connecting to the IP address indicated.
INTERNET_STATUS_CONNECTED_TO_SERVER Successfully connected to the IP address.
INTERNET_STATUS_SENDING_REQUEST Sending the information to the server. The lpvStatusInformation parameter is NULL.
INTERNET_STATUS_REQUEST_SENT Successfully sent the information to the server. The lpvStatusInformation parameter is NULL.
INTERNET_STATUS_RECEIVING_RESPONSE Waiting for the server to respond. The lpvStatusInformation parameter is NULL.
INTERNET_STATUS_RESPONSE_RECEIVED Successfully received the response from the server. The lpvStatusInformation parameter is NULL.
INTERNET_STATUS_CLOSING_CONNECTION Closing the connection to the server. The lpvStatusInformation parameter is NULL.
INTERNET_STATUS_CONNECTION_CLOSED The connection to the server has been closed. The lpvStatusInformation parameter is NULL.
INTERNET_STATUS_HANDLE_CREATED Indicates that a new handle has been created for the Internet connection. This handle can be used to cancel a connection that is taking too long.
INTERNET_STATUS_HANDLE_CLOSED Indicates that the connection to which the handle belongs has been closed.
INTERNET_STATUS_REQUEST_COMPLETE Indicates that the asynchronous request is complete.



If you override the OnStatusCallback function, you need to call the AFX_MANAGE_STATE macro immediately upon entering the OnStatusCallback function. A typical use of the OnStatusCallback might look like the following:

// Enable the status callback function
m_misSession.EnableStatusCallback(TRUE);
.
.
.
void CMyInternetSession::OnStatusCallback(DWORD dwContext,
    DWORD dwInternetStatus, LPVOID lpvStatusInformation,
    DWORD dwStatusInformationLength)
{
    // Get the MFC module state (this is required)
    AFX_MANAGE_STATE(AfxGetAppModuleState());
    // What’s the new status of the Internet Session?
    switch (dwInternetStatus)
    {
    // Resolving the server name
    case INTERNET_STATUS_RESOLVING_NAME:
.
.
.

There are two more status and information functions that are members of the CInternetSession class. The first enables you to determine the type of server you are connected to, and the second enables you to get the context ID for a particular CInternetSession object.

The ServiceTypeFromHandle function takes the handle of a CInternetSession object, available from the HINTERNET operator, and returns a DWORD that indicates the service type of the current session. The possible return values are listed in Table 25.6.

Table 25.6 Internet Session Service Types

Return Value Service Type

INTERNET_SERVICE_FTP FTP
INTERNET_SERVICE_HTTP HTTP
INTERNET_SERVICE_GOPHER Gopher
AFX_INET_SERVICE_FILE File

The last of the status and informational functions is the GetContext function. This function takes no parameters and returns a DWORD value, which is the context ID that was either passed in through the CInternetSession constructor, or through the OpenURL function.

CInternetFile

The CInternetFile class is the base class from which all of the other WinInet file classes are descended. It is a descendant of the CStdioFile class, and is also the file class that is used for the FTP service type. It is a full file class, with all the functions you’d expect for reading, writing, and navigating through a file.

Opening and Closing

Opening a CInternetFile object is done through either the OpenURL method of the CInternetSession class, or through one of the connection classes, CHttpConnection, CFtpConnection, or CGopherConnection. These objects and functions will return a pointer to a CInternetFile object, or a CStdioFile object if you are using the OpenURL method to open a local file.

When you are ready or need to close the file, you have several options, depending on the circumstances. If you need to close the file in a clean fashion, you can use the Close method. This will close the file, cleaning up any outstanding data buffers. Also, if the file was opened for writing, it will call the Flush method, which makes sure that all contents of the write buffer have been sent to and received by the server, before closing the file. If you need to close the file and end all communications, ignoring any exception conditions or any unsent data, you can call the Abort method, which truncates all of the file data communications and closes the file for any additional reading or writing.

Reading and Writing

There are two methods for reading and writing data with a CInternetFile object. Which one you use depends partly on whether the file is an ASCII text file or a binary file.

The two basic read/write functions are Read and Write. Both of these functions take a pointer to a buffer as the first parameter, and a UINT containing the size of the buffer as the second parameter. For the Read function, the data read from the CInternetFile object will be placed into this buffer, and the function will return a UINT value specifying how much was read into the buffer. For the Write function, the data to be written to the CInternetFile should be in the buffer pointed to by the first parameter, with the second parameter specifying how much data to write.

Alternatives to the Read and Write functions are the ReadString and WriteString functions. The ReadString function can take a pointer to a CString object, into which it will read the input file. The ReadString function can also take a pointer to a buffer and the size of the buffer, just as with the Read function. The ReadString function will read in up to the total size of the input buffer, until it reaches a newline character. The WriteString function takes a pointer to a null- terminated string and writes the string to the output file on the server.

If you are reading an HTTP file, there is another function that you can use to control how you are maneuvering through the file. The Seek function enables you to provide random access for CHttpFile type files (it is not currently supported for other Internet file types). The Seek function takes two parameters. The first parameter specifies how far to move the file position, and in which direction (positive values move you forward through the file, whereas negative values move you backward), and the second parameter specifies the reference point in the file from which to move. The available values for the reference point parameters are listed in Table 25.7.

Table 25.7 Internet File Seek Starting Positions

Return Value Service Type

CFile::begin Move the current position in the file the specified number of bytes from the start of the file.
CFile::current Move the current position in the file the specified number of bytes from the current position in the file.
CFile::end Move the current position in the file the specified number of bytes from the end of the file.



Controlling the Buffer

There are two more functions for the CInternetFile class that affect how the class performs communications over the Internet. These two functions control the size of the read and write communications buffers. The read and write communications buffers are used to hold data being sent to the server until it can all be sent over the Internet and received by the server, and to hold data being received from the server until your application can read it from the buffer. Both of these two functions, SetWriteBufferSize and SetReadBufferSize, take a single UINT parameter that specifies the new size of the buffer. These functions will return a Boolean value to specify whether or not they were successful at allocating the size buffer specified.

CInternetException

Whenever any of the WinInet classes throw an exception, you will need to use the CInternetException class to handle the exception. The CInternetException class has two member properties that you can use to determine what the error was, and what needs to be done to correct it. The first member, m_dwError, is the identifier of the error that occurred. This can be any base Windows error, defined in WINERROR.H, or an Internet-specific error, defined in WININET.H.

The second member of the CInternetException class is m_dwContext, which contains the context ID of the thread that the exception occurred in. This is the same context ID that was provided during the creation of the CInternetSession instance, or with the OpenURL function.

CInternetConnection

The CInternetConnection class is one of the classes that you’ll rarely use directly. It is the base class for the CHttpConnection, CFtpConnection, and CGopherConnection classes. It’s created by calling the GetHttpConnection, GetFtpConnection, or GetGopherConnection CInternetSession functions.

The CInternetConnection class contains only three functions. The GetContext function can be called to get the context ID of the connection. The GetSession function can be used to get a pointer to the CInternetSession instance that created the connection. And the GetServerName function can be called to get the name of the server to which the connection is open.

Building a Simple WinInet Application

You can use these basic WinInet classes to build a basic application. You can build a basic application around the OpenURL member function of the CInternetSession class. What this application will be able to do is to take a URL entered by the user, and retrieve the file specified. The file will be displayed for the user to see. The version that is included here is intended for working with text files only, but the same principles can be used for working with binary files also.

Creating the Application Shell

For this sample application, you can create a standard dialog-style application using the AppWizard. You don’t need to include support for Windows Sockets, as the WinInet hides all of the socket programming from you, and you also don’t need to include support for ActiveX controls. For the purpose of the sample code, name your project InetSession.

After you have the application shell created, lay out the main dialog window as shown in Figure 25.2. Configure the controls as specified in Table 25.8, attaching variables to the controls using the ClassWizard as specified in Table 25.9.


Figure 25.2  The main dialog layout for the InetSession project.

Table 25.8 Control Property Settings

Object Property Setting

Static text ID IDC_STATIC
Caption &URL
Edit box ID IDC_EURL
Command button ID IDC_BURL
Caption &Open URL
Static text ID IDC_STATIC
Caption Status:
Static text ID IDC_SSTATUS
Caption Idle
Static text ID IDC_STATIC
Caption Result:
Edit box ID IDC_ERESULT
Multiline Checked
Horizontal scroll Checked
Vertical scroll Checked
Read-only Checked
Command button ID IDCANCEL
Caption &Close

Table 25.9 Control Variables

Object Name Category Type

IDC_ERESULT m_sResult Value CString
IDC_EURL m_sUrl Value CString
IDC_SSTATUS m_sStatus Value CString



After you have finished adding the controls to the dialog window and have attached variables to all the controls, you are now ready to dig into the program code. You’ll start by creating a descendant to the CInternetSession class and then move on to add the main application code that manipulates the Internet functionality.

Inheriting the CInternetSession Class

With this application, you’ll use the OnStatusCallback function to display the status of the application communications. This requires that you create your own descendant of the CInternetSession class. For this sample application, name your descendant class CMyInternetSession. Declare the CInetSessionDlg class before the definition of your new class, as in Listing 25.1.

Listing 25.1 The CMyInternetSession Class Definition


class CInetSessionDlg;

class CMyInternetSession : public CInternetSession
{
public:
    CMyInternetSession();
    virtual ~CMyInternetSession();
};

Add a member variable to your new class, a pointer to the dialog class. Specify the type as CInetSessionDlg*, the name as m_pParentDialog, and the access as private. Next, add a function to initialize this pointer, named SetParentDialog ( CInetSessionDlg* pParentDialog), and use this function to set the m_pParentDialog pointer, as in Listing 25.2.

Listing 25.2 The SetParentDialog Function


void CMyInternetSession::SetParentDialog(
        CInetSessionDlg *pParentDialog)
{
    // Set the parent dialog pointer
    m_pParentDialog = pParentDialog;
}

Finally, add the virtual override function OnStatusCallback, with the parameters specified earlier. Fill in this function to pass a description of the current status to the parent dialog as in Listing 25.3. You’ll use a dialog function that you haven’t defined yet to update the status display.

Listing 25.3 The OnStatusCallback Function


void CMyInternetSession::OnStatusCallback(DWORD dwContext,
    DWORD dwInternetStatus, LPVOID lpvStatusInformation,
    DWORD dwStatusInformationLength)
{
    // Get the MFC module state (this is required)
    AFX_MANAGE_STATE(AfxGetAppModuleState());

    CString sNewStatus;
    CString sStatusInfo((LPCSTR)lpvStatusInformation);

    // Do we have a valid pointer to the parent dialog?
    if (m_pParentDialog)
    {
        // What’s the new status of the Internet Session?
        switch (dwInternetStatus)
        {
        // Resolving the server name
        case INTERNET_STATUS_RESOLVING_NAME:
            if (dwStatusInformationLength > 0)
                sNewStatus = “Resolving name - ” + sStatusInfo;
            else
                sNewStatus = “Resolving name”;
            break;

        // Server name has been resolved
        case INTERNET_STATUS_NAME_RESOLVED:
            if (dwStatusInformationLength > 0)
                sNewStatus = “Name resolved - ” + sStatusInfo;
            else
                sNewStatus = “Name resolved”;
            break;

        // Connecting to the server
        case INTERNET_STATUS_CONNECTING_TO_SERVER:
            sNewStatus = “Connecting to server”;
            break;

        // Connected to the server
        case INTERNET_STATUS_CONNECTED_TO_SERVER:
            sNewStatus = “Connected to server”;
            break;

        // Sending the request
        case INTERNET_STATUS_SENDING_REQUEST:
            sNewStatus = “Sending request”;
            break;

        // Request has been sent
        case INTERNET_STATUS_REQUEST_SENT:
            sNewStatus = “Request sent”;
            break;

        // Receiving the response from the server
        case INTERNET_STATUS_RECEIVING_RESPONSE:
            sNewStatus = “Receiving response”;
            break;

        // The response has been received
        case INTERNET_STATUS_RESPONSE_RECEIVED:
            sNewStatus = “Response received”;
            break;

        // Closing the connection
        case INTERNET_STATUS_CLOSING_CONNECTION:
            sNewStatus = “Closing connection”;
            break;

        // The connection has been closed
        case INTERNET_STATUS_CONNECTION_CLOSED:
            sNewStatus = “Connection closed”;
            break;

        // A handle to the Internet Status has been created
        case INTERNET_STATUS_HANDLE_CREATED:
            sNewStatus = “Handle created”;
            break;

        // Closing the handle of the Status
        case INTERNET_STATUS_HANDLE_CLOSING:
            sNewStatus = “Closing handle”;
            break;

        // Request has been completed
        case INTERNET_STATUS_REQUEST_COMPLETE:
            sNewStatus = “Request complete”;
            break;

        // Unknown status code
        default:
            sNewStatus = “Unknown Status”;
            break;

        }
        // Update the dialog with the new status
        m_pParentDialog->UpdateStatus(sNewStatus);
    }
}

Starting and Stopping the Internet Session

To initialize your Internet session, you need to create an instance of your Internet session class. This can be done very simply by declaring a variable of your class type in the dialog class of your application. Add this variable to the CInetSessionDlg class, specifying the variable type as CMyInternetSession, the variable name as m_misSession, and the access as private.

Starting the Internet session is a very simple matter that can be handled in your application initialization. In the OnInitDialog function, you can add the code in Listing 25.4.

Listing 25.4 The OnInitDialog Function


BOOL CInetSessionDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
.
.
.
.
    // TODO: Add extra initialization here

    // Set the Session class’s pointer to this dialog window
    m_misSession.SetParentDialog(this);

    // Enable the status callback function
    m_misSession.EnableStatusCallback(TRUE);

    // Initialize the screen variables
    m_sStatus = “Waiting”;
    m_sUrl = “http://www.microsoft.com/”;
    m_sResult = “”;
    UpdateData(FALSE);

    return TRUE;  // return TRUE  unless you set the focus to a control
}

In this bit of code, you initialized the Internet session class pointer to the dialog with the SetParentDialog function. Next you enabled the use of the OnStatusCallback function with the EnableStatusCallback function. The remaining lines of the OnInitDialog function are used for initializing the variables that you defined and attached to screen controls earlier.

To close the Internet session, attach a function to your Close button. In this function, you’ll call the CInternetSession Close function, and then call the ancestor OnCancel function to close the application, as in Listing 25.5.

Listing 25.5 The OnCancel Function


void CInetSessionDlg::OnCancel()
{
    // TODO: Add extra cleanup here

    // Close the Internet Session
    m_misSession.Close();

    // Close the dialog
    CDialog::OnCancel();
}

To enable the status display to function properly, you need to add a function to the dialog class to receive the status description and update the display with it. To add this functionality, add a new member function to the CInetSessionDlg class, specify the function type as void, its name as UpdateStatus(CString sNewStatus), and its access as public. This gives you the function definition as you used it back in Listing 25.3. Add the code in Listing 25.6 to this new function.

Listing 25.6 The UpdateStatus Function


void CInetSessionDlg::UpdateStatus(CString sNewStatus)
{
    // Update the screen with the current status
    m_sStatus = sNewStatus;
    UpdateData(FALSE);
}



Retrieving the URL Specified

The heart of your application is the retrieval of the URL file. This will be done using the OpenURL function. When you have a file pointer back from the OpenURL function, you can read lines of text from this file until the end of the file has been reached. To add this last bit of functionality to your application, add a function to the Open URL button using the ClassWizard. Add the code provided in Listing 25.7 to this function.

Listing 25.7 The OnBurl Function


void CInetSessionDlg::OnBurl()
{
    CStdioFile* fpUrlFile;
    CString sCurLine;
    // TODO: Add your control notification handler code here

    // Get the screen control variables
    UpdateData(TRUE);

    // Open the URL specified
    fpUrlFile = m_misSession.OpenURL(m_sUrl);

    // Did we receive a valid Internet file pointer?
    if (fpUrlFile)
    {
        // Read lines of text from the file until all has
        // been received
        while (fpUrlFile->ReadString(sCurLine))
        {
            // Add the new line to the text control
            // and sync with the dialog screen.
            m_sResult += sCurLine;
            UpdateData(FALSE);
        }
    }
}

Before compiling and running your application, make sure that your CMyInternetSession code files include all the other class header files in your project. The first part of your CMyInternetSession.CPP file should look like Listing 25.8. You’ll also want to make sure that the MyInternetSession.h file is included in the dialog code, either in the source code file or the header file. (If you used the wizards throughout this example, the header for your Internet session class might be included in the header file for your dialog class.)

Listing 25.8 The CMyInternetSession.CPP include Section


#include “stdafx.h”
#include “InetSession.h”
#include “MyInternetSession.h”
#include “InetSessionDlg.h”
.
.
.

At this point you should be ready to compile and run your application, downloading HTML text files as shown in Figure 25.3.


Figure 25.3  The running Internet session application.

Application-Level Functionality

The WinInet classes that we’ve looked at so far are the base classes, from which the application-level classes are descended. The application-level classes provide application-specific functionality. The HTTP classes can only be used to connect to and interact with a Web HTTP server. The FTP classes can only be used with FTP servers. And the same with the Gopher classes. It is at this level that the WinInet classes gain a lot of flexibility and usefulness.

HTTP Classes

The HTTP classes enable you to incorporate HTTP-specific functionality into your applications with ease. Because of the simplicity of the HTTP protocol, the connection class is fairly simple, with only a single member function. The file class, however, has quite a number of functions dealing with the intricacies of the HTTP-specific use of the MIME message format.

CHttpConnection

The CHttpConnection class contains only a single member function, OpenRequest. This function does have two versions, with only a subtle difference between the two. The first parameter is the only difference between the two versions, and that parameter is the HTTP command being sent to the server in both versions.

In the first version of the OpenRequest function, the HTTP command is passed as a pointer to a string containing the HTTP command. If NULL is provided for this parameter, the GET command is assumed as a default. The second version of the OpenRequest function takes an integer constant that specifies the HTTP command to be sent to the server. The possible values of this parameter are listed in Table 25.10.

Table 25.10 HTTP Command Parameter Values

HTTP Command Integer Value

HTTP_VERB_POST 0
HTTP_VERB_GET 1
HTTP_VERB_HEAD 2
HTTP_VERB_PUT 3
HTTP_VERB_LINK 4
HTTP_VERB_DELETE 5
HTTP_VERB_UNLINK 6

Starting with the second parameter, the two versions of the OpenRequest function are identical. The second parameter is a string containing the target object, normally a file, of the specified HTTP command. The third parameter is a string containing the URL of the document where the current request originated. If you pass NULL for this parameter, no HTTP header is specified.

The fourth parameter is the context ID for this operation. This will be returned in the OnStatusCallback for identifying this particular transaction. The fifth parameter is a string containing the accept types for your application. This is a string containing a list of the MIME file types that your application can accept and understand. This won’t prevent the Web server from sending you a file type that your application doesn’t understand, but it may have some effect on CGI processes that are called using the PUT and POST commands.

The sixth parameter is a string specifying which version of the HTTP protocol your application supports. If you pass a NULL for this parameter, the supported version is assumed to be HTTP/1 and that is what is sent to the server. The seventh and final parameter is a flag value that can be used to set various options on how the command is processed. The possible values that can be used for this flag are listed in Table 25.11.

Table 25.11 OpenRequest Options

Value Description

INTERNET_FLAG_RELOAD Retrieve the file from the server, even if cached locally.
INTERNET_FLAG_DONT_CACHE Don’t cache the file, either locally or on any gateway or server.
INTERNET_FLAG_SECURE Applicable for HTTP URLs only, this flag specifies to use Secure Sockets Layer or PCT for a secure transaction.
INTERNET_FLAG_MAKE_PERSISTENT Adds the returned object to the local cache as a persistent entity that does not get removed when the cache is cleared.
INTERNET_FLAG_NO_AUTO_REDIRECT Specifies that HTTP redirects should not be automatically handled.



If you need to extract the server and object from a URL for use with the OpenRequest function, you can use the global function AfxParseURL. The AfxParseURL function takes the entire URL as the first parameter, and parses it into its various parts. The protocol is returned in the second parameter, which is a pointer to a DWORD variable. The protocol is returned indicating the service type using the constants listed in Table 25.12.

Table 25.12 AfxParseURL Protocol Types

Value Protocol

AFX_INET_SERVICE_FTP FTP
AFX_INET_SERVICE_HTTP HTTP
AFX_INET_SERVICE_HTTPS HTTPS (HTTP with Secure Sockets Layer)
AFX_INET_SERVICE_GOPHER Gopher
AFX_INET_SERVICE_FILE Local file
AFX_INET_SERVICE_MAILTO SMTP email
AFX_INET_SERVICE_NEWS Usenet News
AFX_INET_SERVICE_NNTP NNTP (Usenet News)
AFX_INET_SERVICE_TELNET Telnet (Network terminal session)
AFX_INET_SERVICE_WAIS WAIS
AFX_INET_SERVICE_MID MID
AFX_INET_SERVICE_CID CID
AFX_INET_SERVICE_PROSPERO PROSPERO
AFX_INET_SERVICE_AFS AFS
AFX_INET_SERVICE_UNK Unknown

The third parameter is a pointer to a string into which the server name will be placed. The fourth parameter is a pointer to a string into which the object to be retrieved will be placed. The fifth parameter is a pointer to a WORD variable (declared as an INTERNET_PORT type), into which the port number will be placed. A typical use of the AfxParseURL and OpenRequest functions is as follows:

CHttpConnection* pConnect;
CHttpFile* pFile;
CInternetSession isSession;
CString sServer;
CString sURL;
CString sObjectName;
INTERNET_PORT wPort;
DWORD dwProtocol;

sURL = “http://www.myserver.com/this_file.htm”;
if (AfxParseURL(sURL, &dwProtocol, &sServer, &sObjectName, &wPort);
{
   if (dwProtocol == AFX_INET_SERVICE_HTTP)
   {
      pConnect = isSession.GetHttpConnection(sServer, wPort);
      if (pConnect)
      {
         pFile = pConnect->OpenRequest(“GET”, (LPCSTR)sObjectName);
.
.
.

There is also the AfxParseURLEx function, which will also extract the username and password from the URL, assuming that they are included.

CHttpFile

The CHttpFile class is created whenever you use the OpenRequest method of the CHttpConnection class, or the OpenURL method of the CInternetSession class, requesting an HTTP resource. When you have a valid CHttpFile object, you can customize the request that is being sent to the Web server. The request hasn’t actually been sent to the server yet; it’s just been created and packaged in the form of a CHttpFile object. From here, you can add additional headers, attach data to the request for sending with a POST or PUT command, and so on.

Packaging and Sending a Request

When you have a valid CHttpFile object, you can add additional headers to the request before sending it by using the AddRequestHeaders function. There are two versions of this function, differing in how you supply the headers to the request. The first version takes a pointer to a string buffer containing the new headers as the first parameter, with the size of the buffer as the third parameter. The second version takes a pointer to a CString as the first parameter. If you are including multiple headers in this request, each header must be separated by a CR/LF (Carriage Return/Line Feed) combination. With both versions of this function, the second parameter is a flag that indicates what to do with the headers passed in. The available values for this flag are listed in Table 25.13.

Table 25.13 AddRequestHeaders Option Values

Value Description

HTTP_ADDREQ_FLAG_COALESCE Merges headers of the same name.
HTTP_ADDREQ_FLAG_REPLACE Replaces headers of the same name. If the header value is found, and the new header value is empty, the header is removed. Can only supply a single header with this option.
HTTP_ADDREQ_FLAG_ADD_IF_NEW Adds the header only if it does not already exist.
HTTP_ADDREQ_FLAG_ADD Used with REPLACE; adds the header if it doesn’t already exist.



When you have everything ready to send, you have two options for sending the actual request to the server, the SendRequest and SendRequestEx functions. The SendRequest function takes as the first argument a list of the names of the headers to send with the request. If you pass a pointer to this list, you need to pass the length of the string of header names as the second parameter. If you pass a CString as the list of headers to send, you don’t need to specify the length of the string. The next parameter that you can supply is a pointer to a buffer containing any data to be sent with a POST or PUT command, followed by a DWORD indicating the size of the buffer to be sent.

The SendRequestEx function enables you to use the Write and WriteString member functions of the CInternetFile class to include the data to be sent using the POST or PUT command. The catch is that you need to specify with the SendRequestEx function the total length of the data to be sent. When you have sent all of the data, you need to close the request using the EndRequest function.

Retrieving Information About a Request

After you have sent your request to the Web server, you can retrieve information about the returned object. You can use several CHttpFile functions to get information from the response.

The QueryInfo function can be used to retrieve any of the headers that were included in the response received from the Web server. The QueryInfo function takes an indicator value as the first parameter that indicates the level of the header requested. The possible values for this indicator are listed in Table 25.14.

Table 25.14 QueryInfo Header Level Values

Value Description

HTTP_QUERY_CUSTOM Finds the requested header and, if found, returns its value in the buffer pointed to by the second parameter.
HTTP_QUERY_FLAG_REQUEST_HEADERS Queries the request headers sent by your application to the server.
HTTP_QUERY_FLAG_SYSTEMTIME For those headers whose value is a date/time string, returns the value as a standard Win32 SYSTEMTIME structure.
HTTP_QUERY_FLAG_NUMBER For those headers whose value is a number, returns the value as a 32-bit number.

The second parameter can vary depending on the type of header requested and the header level specified in the first parameter. The generic parameter is a pointer to a buffer to receive the value, followed by the size of the buffer. If the value requested is a date/time, and the HTTP_QUERY_FLAG_SYSTEMTIME level was requested, the second parameter needs to be a pointer to a SYSTEMTIME structure. If the value is a string value, you also have the option of passing a pointer to a CString object. The final parameter is a pointer to a DWORD, specifying the index of a header, for use with retrieving multiple headers with the same name.

If you want to get the HTTP status code of the request, you can use the QueryInfoStatusCode function. This function takes a pointer to a DWORD variable into which the request status code will be placed. Some common HTTP request status codes are listed in Table 25.15.

Table 25.15 Common HTTP Status Code Values

Value Description

200 URL located, transmission follows
400 Unintelligible request
404 Requested URL not found
405 Server does not support the requested method
500 Unknown server error
503 Server capacity reached

If you want to check to see what HTTP command is being sent with your request, you can use the GetVerb function, which returns a CString containing the command. Likewise, the GetObject function returns the object name being requested from the server, or you can use the GetFileURL function to return the entire URL requested. All three of these functions take no parameters and return a CString.

When you are finished with the HTTP request, call the Close function to close the request and free all resources allocated to it.

FTP Classes

The FTP classes provide you with FTP-specific functionality. The CFtpConnection class provides you with the server navigation functionality necessary to move around the FTP server. The CFileFind and CFtpFileFind classes provide you with the ability to get listings of the files in various directories on the FTP server.

CFtpConnection

The methods of the CFtpConnection class can be broken down into two basic areas of functionality: directories and files. The directory methods provide you with navigation and directory management functionality. The file methods enable you to move, rename, put, and get files—depending on your permissions on the FTP server.

Directories and Navigation

The directory functions for the CFtpConnection class can be used to navigate around the FTP server, or to create or delete directories. To start with, you probably will want to find out what directory you are currently in on the FTP server. This can be done with one of two functions. The GetCurrentDirectory or GetCurrentDirectoryAsURL functions both will take a pointer to a CString object as their only parameter. Both of these functions place the current directory into the CString object. The GetCurrentDirectoryAsURL function formats the directory name as a URL, where the GetCurrentDirectory only gives you the directory and path.

In order to navigate around the directories on the FTP server, you can use the SetCurrentDirectory function. This function takes a string pointer, specifying the directory to move to as the only parameter. It returns a Boolean value, indicating whether it was successful or not.

If you need to create or remove a directory from the current directory on the FTP server, you can use the CreateDirectory or RemoveDirectory functions respectively. Both of these functions return a Boolean value to indicate whether or not they were successful. The possibility of success or failure for these functions is primarily due to the security on the FTP server, and whether the user of your application is logged in to the server and has adequate permission to create, remove, or even navigate to a specific directory.



Working with Files

The functions for putting and getting files from the FTP server are all-inclusive. They eliminate any file manipulating on your application’s part. You specify the filename on the FTP server, and the filename on your local computer, and the CFtpConnection object takes it from there.

For instance, if you want to copy a file that you have on your computer to an FTP server to which you are connected, you can use the PutFile function. The first parameter that you have to pass the PutFile function is the name of the local file to send to the FTP server. The second parameter is the name to be given to the file on the FTP server. The third parameter specifies which mode is to be used in the transfer, whether to send the file as a binary file (FTP_TRANSFER_TYPE_BINARY), or as an ASCII text file (FTP_TRANSFER_TYPE_ASCII). The fourth parameter is the context ID that can be used to track the process using the CInternetSession object.

If you want to get a file from the FTP server, the GetFile function makes it almost as easy. For the GetFile function, the first two parameters are reversed, the first being the filename on the FTP server, and the second being the name to use on the local computer. The third parameter is a Boolean value indicating whether the function should fail if the local filename specified already exists. The fourth parameter is a flag used to indicate any options to use for the local file to be created. The list of these options is provided in Table 25.16. The fifth parameter specifies whether to send the file as binary or ASCII, using the same two values as in the PutFile function. And finally, the sixth parameter is the context ID to be used to identify the transaction with the CInternetSession status monitoring functions.

Table 25.16 FTP GetFile Options

Value Description

FILE_ATTRIBUTE_ARCHIVE The file is to be marked as an archive file.
FILE_ATTRIBUTE_COMPRESSED The file is to be compressed.
FILE_ATTRIBUTE_DIRECTORY The file is a directory.
FILE_ATTRIBUTE_NORMAL The file has no other attributes set. This is the default setting and must be used alone.
FILE_ATTRIBUTE_HIDDEN The file is hidden.
FILE_ATTRIBUTE_READONLY The file is to be marked as read-only.
FILE_ATTRIBUTE_SYSTEM The file is to be used exclusively by the operating system.
FILE_ATTRIBUTE_TEMPORARY The file is being used for temporary storage.

If you need to open a file on the FTP server for reading or writing, you can use the OpenFile function. The OpenFile function takes the filename on the FTP server as the first parameter. The second parameter indicates whether the file should be opened for reading (GENERIC_READ) or writing (GENERIC_WRITE), but cannot be opened for both. The third parameter indicates whether the file should be opened in binary or ASCII mode, using the same indicators as with the GetFile and PutFile functions. Finally, the fourth parameter is the context ID to be used for monitoring the status of the transaction. The OpenFile function returns a pointer to a CInternetFile object, which you can use to read or write to the file.

When you need to manage the files on an FTP server, you have two functions available to use. If you need to rename a file on the FTP server, you can use the Rename function. The Rename function takes two parameters, the first being the current name of the file, and the second being the new name for the file. The function returns a Boolean value indicating whether it was able to rename the file. If you need to delete a file from the FTP server, you can use the Remove function, giving the filename as the only parameter. Like the Rename function, the Remove function returns a Boolean value to indicate its success or failure.

When you have finished your FTP session, you can close the connection using the Close method. It takes no parameters and doesn’t return any indicators of success or failure.

CFileFind

The CFileFind class is the base class for both the CFtpFileFind and CGopherFileFind classes. It provides a lot of the basic functionality necessary for the two descendant classes for enumerating the files in a directory on an FTP or a Gopher server, and getting the various attributes of the files and directories. You do not create a CFileFind object directly, but instead create either a CFtpFileFind or CGopherFileFind object.

File Operations

To find the files in a specified directory, you have to start by calling the FindFile function. The first parameter to the FindFile function is the mask to use in searching for matching files. If you pass NULL for this parameter, the FindFile function does a wildcard search (assuming a mask of *.*). The second parameter is currently unused, and must be passed a value of 0. If you want to find all the files in the current directory on either the FTP or Gopher server, you can get away without providing any parameters for the FindFile function, allowing it to use the default values.

After you have called the FindFile function, you have to call the FindNextFile function to retrieve all of the files in the directory. You place your application into a loop, calling FindNextFile repeatedly, until it returns FALSE, indicating that all matching files in the directory have been found. The FindNextFile function does not take any parameters, and continues the search started with the FindFile function.


Tip:  

When you are using the FindFile and FindNextFile functions, you will use the CFileFind attribute functions to get the name and other attributes of the current file (these functions will be covered next). You won’t have a current file to check the attributes of until you call the FindNextFile for the first time. The FindFile function starts the search, but doesn’t position the search at the first file.


When you have finished finding all of the matching files in your search, you need to call the Close method to close the search before you can start another search using the CFileFind class.



File Attributes

The file attribute functions are called to get specific attributes of a file, such as the filename or size. Most of these functions do not take any parameters, and return an appropriate data type for the type of attribute being requested. All of the attribute functions return the attribute specified in the function name. All of the attribute functions are listed in Table 25.17.

Table 25.17 FTP GetFile Options

Function Return Type Parameter Description

GetLength DWORD None Returns the size of the file, in bytes.
GetFileName CString None Returns the name of the file, including the extension.
GetFilePath CString None Returns the whole path of the file.
GetFileTitle CString None Returns the name of the file without the extension.
GetFileURL CString None Returns the URL of the file.
GetRoot CString None Returns the directory in which the file was found.
GetCreationTime BOOL CTime& Places the time that the file was created in the CTime object passed in as a parameter.
GetLastAccessTime BOOL CTime& Places the time that the file was last accessed in the CTime object passed in as a parameter.
GetLastWriteTime BOOL CTime& Places the time that the file was last written to in the CTime object passed in as a parameter.
IsDots BOOL None Indicates if the filename is . or .., indicating that it is a directory.
IsReadOnly BOOL None Indicates whether the file is read-only.
IsDirectory BOOL None Indicates whether the file is a directory.
IsCompressed BOOL None Indicates whether the file is compressed.
IsSystem BOOL None Indicates whether the file is a system file.
IsHidden BOOL None Indicates whether the file is hidden.
IsTemporary BOOL None Indicates whether the file is a temporary file.
IsNormal BOOL None Indicates whether the file is normal with no other attributes.
IsArchived BOOL None Indicates whether the file is archived.

CFtpFileFind

The CFtpFileFind is used to perform the FTP-specific file-finding functionality. It is created by passing a pointer to an open CFtpConnection object in the constructor. The other parameter that you can provide to the CFtpFileFind constructor is the context ID to be used for monitoring the status of its operations.

After you have created a valid CFtpFileFind object, you can call its variation on the FindFile function to start the file search. The CFtpFileFind version of the FindFile function takes two parameters. The first parameter is the mask to be used in the search. If you provide NULL for this parameter, a wildcard search is assumed. The second parameter is a flag that is used to control how the search is performed. The available values for this flag are listed in Table 25.18.

Table 25.18 CFtpFileFind.FindFile Options

Value Description

INTERNET_FLAG_RELOAD Retrieve the file from the server, even if cached locally.
INTERNET_FLAG_DONT_CACHE Don’t cache the file, either locally or on any gateway or server.
INTERNET_FLAG_EXISTING_CONNECT If possible, reuse the connection to the server for new requests generated with the FindFile function, instead of opening a new connection for each request.
INTERNET_FLAG_RAW_DATA Override the defaults to return the raw data.



A typical usage of the CFtpFileFind class and the underlying CFindFile functionality is as follows:

CFtpConnection* m_fcConnection;
.
.
.
CFtpFileFind fFiles(m_fcConnection);
CString sFileName;
BOOL bMoreFiles;

// Find the first file
bMoreFiles = fFiles.FindFile(NULL);
// Loop as long as there are additional files
while (bMoreFiles)
{
    // Find the next file
    bMoreFiles = fFiles.FindNextFile();
    // Get the file name
    sFileName = fFiles.GetFileName();
    // Is the file a directory?
    if (fFiles.IsDirectory())
        // Mark it as a directory
        sFileName += “     <DIR>”;
    // Do whatever with the file
    .
    .
    .
}
fFiles.Close();

Gopher Classes

When you are working with a Gopher server, it takes a slightly different approach than with either an FTP or HTTP server. Gopher servers require what’s known as a locator for retrieving a file from the server. The locator is passed around the various classes and member functions of the Gopher classes as a token that is used to maintain the context of the session between the client and the server.

CGopherConnection

After you have created a CGopherConnection object from the CInternetSession object, you can use it to create a Gopher locator and open a file, but you more likely will create a CGopherFileFind object and then use it to create the locator to use to open and download a file.

If you know the name of the file that you want to open on the Gopher server, you can use it to create a locator using the CreateLocator function. There are three versions of the CreateLocator function. The first version of the CreateLocator function requires a pointer to the name of the document or directory on the Gopher server to be retrieved. The second parameter is the selector string to be used. Either of these two parameters may be NULL, specifying that the default directory of the Gopher server be used. The third parameter specifies whether the object being requested is a directory or a document.

The second version of the CreateLocator function requires a single parameter, a pointer to a string indicating the file to open. The third version requires the server name as the first parameter, followed by the parameters (in order) of the first version of the CreateLocator function. The final parameter for the third version is the port to connect to on the Gopher server. All three versions of this function return a CGopherLocator object as the return value.

The next function of the CGopherConnection class is the OpenFile function, which is used to open a document on the Gopher server. It returns a pointer to a CGopherFile object, which can then be used to read the contents of the file. The first parameter the OpenFile function requires is a pointer to a CGopherLocator object. The second parameter is a flag controlling how the file is opened and downloaded. The available settings for this flag can be found earlier in the CInternetSession::OpenURL function. The third parameter is a pointer to a string indicating which view of the file to open. If NULL is provided, the default view is opened. The fourth parameter is the context ID that is used to monitor the status of the transaction.

The final member function of the CGopherConnection class is the GetAttribute function. This function can be used to retrieve attributes of a document on a Gopher server. The first parameter is a pointer to a GOPHER_ATTRIBUTE_TYPE structure, in which the attribute is returned. The second parameter is a CString containing the attributes to be returned. The third parameter is a pointer to a CGopherLocator object.

CGopherLocator

The CGopherLocator class is directly used during a Gopher session, but is passed from function to function as a token. There is one member function of the CGopherLocator class, GetLocatorType, which is passed a pointer to a DWORD variable. The type of the locator is placed into this DWORD variable. The possible locator types are specified in Table 25.19.

Table 25.19 CGopherLocator Types

Value Description

GOPHER_TYPE_TEXT_FILE Indicates that the object is an ASCII text file.
GOPHER_TYPE_DIRECTORY Indicates that the object is a directory containing more Gopher items.
GOPHER_TYPE_CSO The object is a CSO phone book server.
GOPHER_TYPE_ERROR Indicates that an error condition has occurred.
GOPHER_TYPE_MAC_BINHEX The object is a Macintosh file in BINHEX format.
GOPHER_TYPE_DOS_ARCHIVE The object is a DOS archive file.
GOPHER_TYPE_UNIX_UUENCODED The object is a UUENCODED file.
GOPHER_TYPE_INDEX_SERVER The object is an index server.
GOPHER_TYPE_TELNET The object is a Telnet server that can be connected to using a Telnet terminal session.
GOPHER_TYPE_BINARY The object is a binary file (unknown file type).
GOPHER_TYPE_REDUNDANT The object is a duplicated server.
GOPHER_TYPE_TN3270 The object is a TN3270 server, which can be connected to using a TN3270 terminal emulator.
GOPHER_TYPE_GIF The object is a GIF graphics file.
GOPHER_TYPE_IMAGE The object is a generic image file.
GOPHER_TYPE_BITMAP The object is a bitmap file.
GOPHER_TYPE_MOVIE The object is a movie file.
GOPHER_TYPE_SOUND The object is a sound file.
GOPHER_TYPE_HTML The object is an HTML document.
GOPHER_TYPE_PDF The object is an Adobe PDF file.
GOPHER_TYPE_CALENDAR The object is a calendar file.
GOPHER_TYPE_INLINE The object is an inline file.
GOPHER_TYPE_UNKNOWN The object is an unknown file type.
GOPHER_TYPE_ASK The object is an Ask+ item.
GOPHER_TYPE_GOPHER_PLUS The object is a Gopher+ item.



CGopherFileFind

The CGopherFileFind is used to perform the Gopher-specific file-finding functionality. It is created by passing a pointer to an open CGopherConnection object in the constructor. The other parameter that you can provide to the CGopherFileFind constructor is the context ID to be used to monitor the status of its operations.

After you have created a valid CGopherFileFind object, you can call its variation on the FindFile function to start the file search. There are two versions of the FindFile function in the CGopherFileFind class. The first version of the FindFile function is the same as with the CFtpFileFind class, using the same options. The second version requires a pointer to a CGopherLocator object as the first parameter, and is the same for the next two parameters.

After you have started a file find, you can traverse the list of files in the same way that you do with an FTP file find. When you have located a file, you can call the GetLocator function to create the CGopherLocator object that was used to find the file, and the GetScreenName function to get the screen name for the selected file.

CGopherFile

After you have opened a file on the Gopher server using the OpenFile function, you can read the file using the standard CInternetFile or CStdioFile functions. The CGopherFile class extends some of these functions to provide Gopher-specific functionality, but doesn’t add any new functions, or alter how you need to use any of the underlying functions.

Building a Simple FTP Client

To see how the application-level classes work, you can build a sample FTP client application. This will be a very rudimentary FTP client, showing only the files on the server side, and only allowing you to retrieve files. It will also require you to type in directory names if you want to change directories, instead of selecting the directory name in the list of available files to automatically be taken to the selected directory. All of the necessary functionality to make the sample application a full-featured FTP client is not difficult, and doesn’t take a lot of time or effort.

Creating the Application Shell

For this sample application, you can create a standard dialog-style application using the AppWizard. As with the previous sample application, you don’t need to include support for Windows Sockets, as the WinInet hides all of the socket programming from you, and you also don’t need to include support for ActiveX controls. For the purpose of the sample code, name your project InetFTP.

After you have the application shell created, lay out the main dialog window as shown in Figure 25.4. Configure the controls as specified in Table 25.20, attaching variables to the controls using the ClassWizard as specified in Table 25.21.


Figure 25.4  The main dialog layout for the InetFTP project.

Table 25.20 Control Property Settings

Object Property Setting

Static text ID IDC_STATIC
Caption &Server
Edit box ID IDC_ESERVER
Command button ID IDC_BCONNECT
Caption &Connect
Static text ID IDC_STATIC
Caption &User Name:
Edit box ID IDC_EUSER
Static text ID IDC_STATIC
Caption &Password:
Edit box ID IDC_EPASSWORD
Static text ID IDC_STATIC
Caption &Directory:
Edit box ID IDC_EDIRECTORY
Command button ID IDC_BSETDIR
Caption S&et Directory
Static text ID IDC_STATIC
Caption &Files:
List box ID IDC_LFILES
Sort Checked
Vertical Scroll Checked
Command button ID IDC_BGETFILE
Caption &Get File
Command button ID IDC_BCLOSE
Caption E&xit



Table 25.21 Control Variables

Object Name Category Type

IDC_BCONNECT m_cltConnect Control CButton
IDC_EDIRECTORY m_sDirectory Value CString
IDC_EPASSWORD m_sPassword Value CString
IDC_ESERVER m_sServer Value CString
IDC_EUSER m_sUser Value CString
IDC_LFILES m_sFile Value CString
IDC_LFILES m_lbFiles Control CListBox

Initializing the Application

In addition to the variables added through the ClassWizard, you’ll need to add two additional variables to the dialog class. These two variables will be an instance of the CInternetSession class and a pointer to a CFtpConnection class. Both of these variables can be private in their scope. For this example, the CInternetSession variable will be named m_isSession, and the pointer to a CFtpConnection object will be named m_fcConnection.

After you have added these variables, you’ll need to initialize all of the application variables. Set all of the control text variables to default values, and make sure the FTP connection pointer is NULL. To do this, add the code in Listing 25.9 to the OnInitDialog function.

Listing 25.9 The OnInitDialog Function


BOOL CInetFTPDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
    // Add “About...” menu item to system menu.
.
.
.
    // Set the icon for this dialog.  The framework does this
    // automatically
    //  when the application’s main window is not a dialog
    SetIcon(m_hIcon, TRUE);         // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon

    // TODO: Add extra initialization here

    // Initialize the form variables
    m_sServer = “ftp.brba.com”;
    m_sDirectory = “”;
    m_sUser = “anonymous”;
    m_sPassword = “user@email.com”;

    // And the FTP connection object
    m_fcConnection = NULL;

    // Update the screen
    UpdateData(FALSE);

    return TRUE;  // return TRUE  unless you set the focus
                  // to a control
}

To clean up the application, attach an event function to the Exit button and include the code in Listing 25.10. This code will close the Internet session, and then close the application.

Listing 25.10 The OnBclose Function


void CInetFTPDlg::OnBclose()
{
    // TODO: Add your control notification handler code here

    // End the Internet session
    m_isSession.Close();

    // Close the application
    OnOK();
}

Connecting to the FTP Server

For this sample application, you’ll use the same button to open the connection to the FTP server and to close the connection. For opening the connection, you’ll have to check to make sure you have a server name to connect to before opening the connection. When you have a connection open, you’ll want to get the name of the current directory, and then get a listing of all of the files in the directory. To get a listing of the files in the directory, you’ll call a function named LoadListOfFiles, which you’ll write in just a minute. To add this functionality, attach an event function to the Connect button and add the code in Listing 25.11.

Listing 25.11 The OnBconnect Function


void CInetFTPDlg::OnBconnect()
{
    // TODO: Add your control notification handler code here

    // Get the data from the screen
    UpdateData(TRUE);

    // Do we have a current connection?
    if (!m_fcConnection)
    {
        // No, do we have a server name?
        if (m_sServer != “”)
        {
            // Yes, open a connection
            m_fcConnection = m_isSession.GetFtpConnection(
                    m_sServer, m_sUser, m_sPassword);

            // Did we get a connection?
            if (m_fcConnection)
            {
                // Yes, get the current directory
                m_fcConnection->GetCurrentDirectory(m_sDirectory);
                // Change the text on the button to close
                // the connection
                m_ctlConnect.SetWindowText(“&Close”);

                // Load the list of files in the current directory
                LoadListOfFiles();

                // Update the screen
                UpdateData(FALSE);
            }
        }
    }
    else    // We have a current connection
    {
        // Close the connection
        m_fcConnection->Close();

        // Delete the connection object
        delete m_fcConnection;

        // Reinitialize the connection object pointer
        m_fcConnection = NULL;

        // Reset the screen controls and variables
        m_ctlConnect.SetWindowText(“&Connect”);
        m_sDirectory = “”;

        // Reset the contents of the directory list
        m_lbFiles.ResetContent();

        // Update the screen
        UpdateData(FALSE);
    }
}



Getting a Listing of Files

For loading the list of files in the current directory, you’ll need to first clear the list box of any files already listed in there. Next, you’ll need to create a CFtpFileFind object from the current FTP connection object. When you have an FTP file find object, find the first file using the default wildcard, thus getting a listing of all files. Loop until you’ve found all of the files in the current directory, using the FindNextFile function to loop through each file, getting the filename and checking to see if it’s a directory before adding it to the list box. When all files have been retrieved and added to the list box, close the FTP file finder and exit the function. To add this functionality to the sample application, add a member function to the dialog class named LoadListOfFile, and add the code in Listing 25.12.

Listing 25.12 The LoadListOfFiles Function


void CInetFTPDlg::LoadListOfFiles()
{
    // Reset the contents of the list of files
    m_lbFiles.ResetContent();

    // Create a file find object
    CFtpFileFind fFiles(m_fcConnection);
    // And variables to keep track of the current file name
    // and if there are any more files
    CString sFileName;
    BOOL bMoreFiles;

    // Find the first file
    bMoreFiles = fFiles.FindFile(NULL);

    // Loop while there are more files to find
    while (bMoreFiles)
    {
        // Find the next file
        bMoreFiles = fFiles.FindNextFile();
        // Get the file name
        sFileName = fFiles.GetFileName();
        // Is the file a directory?
        if (fFiles.IsDirectory())
            // If so, then specify that it’s a directory
            sFileName += “     <DIR>”;
        // Add the file name to the list of files
        m_lbFiles.AddString((LPCTSTR)sFileName);
    }
    // Close the file find object
    fFiles.Close();
}

Changing Directories and Retrieving Files

For providing the functionality to enable the user to change directories, you’ll want to perform the following actions. First, you’ll want to check to make sure that there is a directory specified. Next, you’ll set the current directory to the specified directory using the SetCurrentDirectory function. After you’ve changed directories, you’ll want to get the current directory name so that the new current directory is displayed for the user in the form the FTP server understands. Finally, you’ll want to call the LoadListOfFiles function to get a listing of the files in the new directory. To add this functionality to the sample application, attach an event function to the Set Directory button and add the code in Listing 25.13.

Listing 25.13 The OnBsetdir Function


void CInetFTPDlg::OnBsetdir()
{
    // TODO: Add your control notification handler code here

    // Get the data from the screen
    UpdateData(TRUE);

    // Do we have a directory name?
    if (m_sDirectory != “”)
    {
        // Move to the specified directory
        m_fcConnection->SetCurrentDirectory((LPCTSTR)m_sDirectory);

        // Get the current directory name (if we were
        // successful, it should be the specified new
        // directory)
        m_fcConnection->GetCurrentDirectory(m_sDirectory);

        // Load the list of files in the new directory
        LoadListOfFiles();

        // Update the screen
        UpdateData(FALSE);
    }
}

The final bit of functionality that you’ll add to the sample application is the capability to retrieve files from the FTP server. To perform this action, you’ll want to get the name of the file currently selected in the list of files. After checking the name of the file to make sure it isn’t a directory, you’ll want to get the local location and name to copy the selected file to. You can use the standard File Save dialog class to perform this task. Finally, you’ll call the FTP connection object’s GetFile function to retrieve the file. To add this functionality to the sample application, attach an event function to the Get File button, and add the code in Listing 25.14.

Listing 25.14 The OnBgetfile Function


void CInetFTPDlg::OnBgetfile()
{
    // TODO: Add your control notification handler code here

    // Get the data from the screen
    UpdateData(TRUE);

    // Do we have a file name?
    if (m_sFile != “”)
    {
        // Is it a directory?
        if (m_sFile.Right(5) == “<DIR>”)
            // If so, warn the user
            MessageBox(“Unable to download directory.”);
        else
        {
            // Get the name of the destination file
            CFileDialog ldFile(FALSE, NULL, (LPCTSTR)m_sFile);
            if (ldFile.DoModal() == IDOK)
            {
                // Get the specified file
                if (!m_fcConnection->GetFile((LPCTSTR)m_sFile,
                        (LPCTSTR)ldFile.GetFileName()))
                    // If unsuccessful, tell the user
                    MessageBox(“Error getting file”);
            }
        }
    }
}

At this point you should be ready to compile and run your application. When you have it running, you’ll find that you have a fully functional, very basic FTP client, as seen in Figure 25.5.


Figure 25.5  The running FTP client application.

Summary

This chapter discusses how you can use the WinInet MFC classes to easily provide extensive Internet functionality using the HTTP (Web), FTP, and Gopher application protocols. It covers the basics of Internet application protocols and how extensive they can be. You also saw how using the MFC WinInet classes can hide a large amount of this complexity from you as an application programmer.

Among the first things covered in this chapter are the basics of WinInet programming using the MFC classes and how an extensive set of functionality is included in the base classes. You saw how you could build an entire Internet-enabled application using the base WinInet classes, without using any of the application-level classes. You saw how you could use the base classes to easily retrieve files from HTTP, FTP, or Gopher servers without delving into the higher classes.

Where the chapter covers the application-level classes, you saw how the application-specific classes could be used to easily provide a fully functional client application for each of the three protocols incorporated in the WinInet API. You saw how easy the WinInet MFC classes make it to build a fully functional FTP client, and how easy it would also be to build a HTTP or Gopher client using these classes.